/**
 * @ author : xia chen hui
 * @ email : 184415359@qq.com
 * @ date : 2020/6/13 20:45
 * @ desc : 处理音频
 */

#include "VideoChannel.h"

void *task_video_decode(void *pVoid) {
    VideoChannel *videoChannel = static_cast<VideoChannel *>(pVoid);
    videoChannel->video_decode();

    return 0;
}

void *task_video_player(void *pVoid) {
    VideoChannel *videoChannel = static_cast<VideoChannel *>(pVoid);
    videoChannel->video_player();

    return 0;
}

VideoChannel::VideoChannel(int streamIndex, AVCodecContext *avCodecContext)
        : BaseChannel(streamIndex, avCodecContext) {

}

VideoChannel::~VideoChannel() {

}

// 从队列中取出 1.解码 2. 播放
void VideoChannel::start() {
    this->isPlaying = 1;
    // 存放未解锁的队列 开始工作。
    this->packets.setFlag(1);
    //存放已解码的队列，开始工作
    this->frames.setFlag(1);
    //解码的线程
    pthread_create(&pid_video_decode, 0, task_video_decode, this);

    // 播放的线程
    pthread_create(&pid_video_player, 0, task_video_player, this);


}

void VideoChannel::stop() {

}

//异步线程，开始解码
void VideoChannel::video_decode() {
    //取出未解码的队列数据
    AVPacket *packet;
    while (isPlaying) {
        // 消费速度比生成速度慢（生成100，只消费10个，这样队列会爆）
        // 内存泄漏点2，解决方案：控制队列大小
        if (isPlaying && frames.queueSize() > 100) {
            // 休眠 等待队列中的数据被消费
            av_usleep(10 * 1000);
            continue;
        }

        int ret = this->packets.pop(packet);
        if (!isPlaying) {
            break;
        }
        if (!ret) {
            continue;
        }
        //可以未解码的视频数据包
        ret = avcodec_send_packet(this->avCodecContext, packet);
        if (ret) {
            //失败了
            break;
        }
        //创建原始数据 ，开始把音频AAC 视频H264转变成音频PCM 视频YUV了
        AVFrame *avFrame = av_frame_alloc();
        ret = avcodec_receive_frame(this->avCodecContext, avFrame);
        if (ret == AVERROR(EAGAIN)) {
            //代表帧取的不完整 如果是I帧（完整的一帧）就不会进入，如果是P帧  B帧 就会进入这里
            continue; //重新取，直到取到完整的帧为止
        } else if (ret != 0) {
            // TODO xia chen hui 2020/6/14 23:38 做释放工作
            break;
        }
        //取到了原始数据
        this->frames.push(avFrame);
    }

    // TODO xia chen hui 2020/6/14 23:39 出了循环 ，需要释放
    releaseAVPacket(&packet);
}

//异步线程 开始播放
void VideoChannel::video_player() {
    // yuv最原始的视频帧数据，但是不能显示在屏幕上（Android IOS SDL等）
    //yuv 转变成rgba 才能显示
    // TODO xia chen hui 2020/6/15 0:04 1.转换的上下文 ,把yunv转为rgba   ，SwsContext是视频
    SwsContext *swsContext = sws_getContext(
            //原始的一层 宽  高  格式
            this->avCodecContext->width, this->avCodecContext->height,
            this->avCodecContext->pix_fmt,
            //目标最终要显示到屏幕的信息 ，最好和原始的保持一致
            this->avCodecContext->width, this->avCodecContext->height, AV_PIX_FMT_RGBA,
            //渲染的速率  ，这个是比较常用的
            SWS_BILINEAR,
            NULL, NULL, NULL
    );
    // TODO xia chen hui 2020/6/15 0:04 2.给转换后的数据 rgba 这种申请内存
    uint8_t *dst_data[4];
    int dst_linesize[4];
    AVFrame *avFrame = 0;
    av_image_alloc(dst_data, dst_linesize,
                   avCodecContext->width, avCodecContext->height, AV_PIX_FMT_RGBA, 1);
    // TODO xia chen hui 2020/6/15 0:07 3. 原始数据转换 （队列中拿到原始数据，一帧一帧的转换未rgba，一帧一帧渲染到屏幕）
    int ret;
    while (isPlaying) {
        ret = frames.pop(avFrame);
        if (!isPlaying) {
            //停止播放，就跳出循环，出了循环就需要释放
        }
        if (!ret) {
            //失败了就继续
            continue;
        }
        //avFrame->data是yuv原始数据 dst_data是rgba格式的数据
        sws_scale(swsContext, avFrame->data,
                  avFrame->linesize, 0, avCodecContext->height, dst_data, dst_linesize);
        //渲染 方式有2种，1 渲染一帧图像（宽  高  数据） 这个会回调native-lib中的renderFrame函数
        this->renderCallback(dst_data[0], avCodecContext->width, avCodecContext->height,
                             dst_linesize[0]);

        releaseAVFrame(&avFrame);
    }
    releaseAVFrame(&avFrame);
    isPlaying = 0;
    av_freep(dst_data[0]);
    sws_freeContext(swsContext);
}

void VideoChannel::setRenderCallback(RenderCallback renderCallback) {
    this->renderCallback = renderCallback;
}
